﻿using System;
using System.ComponentModel;
using System.Deployment.Application;
using System.Linq;
using System.Net;
using System.Threading;
using System.Xml.Linq;
using VNet.VisiSuite.Common.Logger;

namespace VNet.VisiSuite.VisiCentral.Common.Helpers
{
    public class AutoUpdate
    {
        public delegate bool OnUpdateAvailable(string err_msg);

        private const int MIN_POLLING_FREQ = 60;
        private readonly AutoResetEvent _stop_worker_event;
        private string _err_msg = "";
        private int _polling_freq = 360;
        private volatile bool _stopped; // was the thread stopped

        // Callback when events are available from the VisiBox hardware.
        private OnUpdateAvailable _todo_when_update_available;
        private BackgroundWorker _worker;

        public AutoUpdate()
        {
            _stop_worker_event = new AutoResetEvent(true);
        }

        ~AutoUpdate()
        {
            Stop();
        }

        public bool Start(int polling_freq, OnUpdateAvailable callback)
        {
            bool success = false;

            try
            {
                if (_worker == null)
                {
                    _todo_when_update_available = callback;
                    _polling_freq = polling_freq < MIN_POLLING_FREQ ? MIN_POLLING_FREQ : polling_freq;
                    _polling_freq *= 1000; // convert to ms
                    _stopped = false;

                    _worker = new BackgroundWorker();
                    _worker.WorkerReportsProgress = true;
                    _worker.WorkerSupportsCancellation = true;
                    _worker.DoWork += DoWork;
                    _worker.ProgressChanged += ReportProgress;
                    _worker.RunWorkerCompleted += WorkComplete;
                    _worker.RunWorkerAsync(null);

                    success = true;
                }
            }
            catch (Exception ex)
            {
                FileLog.Error("Error starting AutoUpdate thread: ", ex);
                success = false;
            }

            return success;
        }

        public bool Stop()
        {
            if (_worker != null)
            {
                _stopped = true;
                _worker.CancelAsync();
                _stop_worker_event.Set();
            }
            return true;
        }

        // Callbacks for BackgroundWorker class
        private void WorkComplete(object sender, RunWorkerCompletedEventArgs e)
        {
            // Log any errors
            if (e.Error != null)
            {
                FileLog.Error("AutoUpdate error - ", e.Error);
            }

            // Did this end prematurely??
            if (!_stopped)
            {
                Thread.Sleep(5000);
                if (!_stopped)
                    _worker.RunWorkerAsync();
            }
        }

        private void DoWork(object sender, DoWorkEventArgs e)
        {
            // REMEMBER THAT THIS HAPPENS IN A SEPARATE THREAD SO WORRY ABOUT THREAD SAFETY

            var worker = sender as BackgroundWorker;

            while (!worker.CancellationPending)
            {
                _stop_worker_event.WaitOne(_polling_freq);

                if (!worker.CancellationPending && UpdateAvailable())
                    worker.ReportProgress(1);
            }

            if (worker.CancellationPending)
                e.Cancel = true;

            e.Result = true;
        }

        private void ReportProgress(object sender, ProgressChangedEventArgs e)
        {
            // This happens in the UI thread
            _todo_when_update_available(_err_msg);
        }

        private bool UpdateAvailable()
        {
            //UpdateCheckInfo info = null;
            var status = UpdateStatuses.NoUpdateAvailable;

            _err_msg = "";

            // Check if the application was deployed via ClickOnce.
            if (!ApplicationDeployment.IsNetworkDeployed)
            {
                status = UpdateStatuses.NotDeployedViaClickOnce;
                //_err_msg = "status: " + status.ToString();
                return false;
            }

            ApplicationDeployment update_check = ApplicationDeployment.CurrentDeployment;

            try
            {
                //Used to use the Clickonce API but we've uncovered a pretty serious bug which results in a COMException and the loss of ability
                //to check for updates. So until this is fixed, we're resorting to a very lo-fi way of checking for an update
                //FileLog.Error("AutoUpdate: " + update_check.UpdateLocation.ToString());

                string manifestFile = new WebClient().DownloadString(update_check.UpdateLocation);
                XDocument xdoc = XDocument.Parse(manifestFile);
                XNamespace nsSys = "urn:schemas-microsoft-com:asm.v1";
                var latest_version =
                    new Version(xdoc.Descendants(nsSys + "assemblyIdentity").First().Attribute("version").Value);

                Version running_version = ApplicationDeployment.CurrentDeployment.CurrentVersion;

                if (latest_version.CompareTo(running_version) > 0)
                    status = UpdateStatuses.UpdateAvailable;

                // FileLog.Error("AutoUpdate: " + latest_version.ToString() + " - " + running_version.ToString());

                // info = update_check.CheckForDetailedUpdate();
            }
            catch (DeploymentDownloadException dde)
            {
                FileLog.Error("AutoUpdate.updateAvailable() - ", dde);
                status = UpdateStatuses.DeploymentDownloadException;
            }
            catch (InvalidDeploymentException ide)
            {
                FileLog.Error("AutoUpdate.updateAvailable() - ", ide);
                status = UpdateStatuses.InvalidDeploymentException;
            }
            catch (InvalidOperationException ioe)
            {
                FileLog.Error("AutoUpdate.updateAvailable() - ", ioe);
                status = UpdateStatuses.InvalidOperationException;
            }
            catch (Exception ex)
            {
                FileLog.Error("AutoUpdate.updateAvailable() - ", ex);
                status = UpdateStatuses.NoUpdateAvailable;
            }

            if (status == UpdateStatuses.UpdateAvailable) //  && info != null && info.UpdateAvailable)
            {
                /**
                if (info.IsUpdateRequired)
                    status = UpdateStatuses.UpdateRequired;
                else
                    status = UpdateStatuses.UpdateAvailable;
                **/

                try
                {
                    _err_msg = "";
                    update_check.Update();
                }
                catch (DeploymentDownloadException dde)
                {
                    _err_msg =
                        "Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " +
                        dde.Message;
                }
            }
            else
            {
                status = UpdateStatuses.NoUpdateAvailable;
            }

            //if (_err_msg == "")
            //    _err_msg = "status : " + status.ToString();

            // return (info != null && (status == UpdateStatuses.UpdateAvailable || status == UpdateStatuses.UpdateRequired));

            return (status == UpdateStatuses.UpdateAvailable || status == UpdateStatuses.UpdateRequired);
        }

        private enum UpdateStatuses
        {
            NoUpdateAvailable,
            UpdateAvailable,
            UpdateRequired,
            NotDeployedViaClickOnce,
            DeploymentDownloadException,
            InvalidDeploymentException,
            InvalidOperationException
        }
    }
}